package org.mihalovic.PES.Core.Graph;



import java.util.Collection;
import java.util.Set;
import java.util.List;

import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.util.EdgeType;



/**
 * @author Martin Mihalovic
 */
public class GraphBuilder
{
    /**
     * Builds and returns <code>DirectedSparseGraph</code> with given vertices and edges.
     * Edge weights are normalized in a way that sum of out-going edges of any single vertex
     * is equal 1.
     *
     * @param vertices <code>List</code> of vertices wrapped in <code>Node</code> objects
     * @param edges <code>List</code> of edges wrapped in <code>Edge</code> objects.
     * The method expects that each edge (as source/target pair) in the <code>List</code>
     * is contained only once. Otherwise an exception is thrown.
     *
     * @return <code>DirectedSparseGraph</code>
     *
     * @throws Soner.Core.Graph.GraphException if some edge refers to node (either on
     * source or target side) not listed in <code>vertices</code> or when one edge is
     * listed more than once in argument edges.
     */
    public DirectedSparseGraph<Node, Edge> Build(Set<Node> vertices, Collection<Edge>edges) throws GraphException
    {
        DirectedSparseGraph<Node /*Integer*/, Edge> g = new DirectedSparseGraph<Node, /*Integer,*/ Edge>();

        for (Node vertex : vertices)
        {
            if(!g.addVertex(vertex))
                throw new IllegalArgumentException("Cannot add node '" +vertex.getName()+ "' (" +vertex.getID()+ ") to the graph");
        }

        for(Edge edge : edges)
        {
            if(g.addVertex(edge.getSourceNode()))          //This should be false when the node is already in graph
                throw new GraphException(   "Vertex '"+edge.getSourceNode().getName()+
                                            "' is not present in the graph. Cannot create edge.",
                                            edge, edge.getSourceNode().getName());
            if(g.addVertex(edge.getTargetNode()))          //This should be false when the node is already in graph
                throw new GraphException(   "Vertex '"+edge.getTargetNode().getName()+
                                            "' is not present in the graph. Cannot create edge.",
                                            edge, edge.getTargetNode().getName());


            try{
                if( !g.addEdge(edge, edge.getSourceNode(), edge.getTargetNode(), EdgeType.DIRECTED) )
                {
                    throw new GraphException("Unable to add new edge (addEdge returned false)", edge);
                }
            }
            catch(IllegalArgumentException iae)
            {
                throw new GraphException("Cannot add edge to the graph", edge, iae);
            }
        }

        normalizeEdgeWeights(g);

        return g;
    }



    /**
     * Normalizes graph's edges so that sum of out-edges of every vertex is 1.
     * This is computed as original edge's weight divided by sum of all edge
     * weights pointing from incident vertex.
     *
     * @param graph directed graph with weighted edges
     */
    private static void normalizeEdgeWeights(DirectedSparseGraph<Node, Edge> graph)
    {
        Collection<Node> vertices = graph.getVertices();

        for(Node v : vertices)
        {
            Collection<Edge> outEdges = graph.getOutEdges(v);
            double weightSum = 0;

            for(Edge edge : outEdges)
                weightSum += edge.getWeight();
            for(Edge edge : outEdges)
                edge.setWeight( edge.getWeight() / weightSum);
        }
    }
}
